//!
/**
 ***********************************************************************************************************
 * <RBEffectCompiler implementation>
 ***********************************************************************************************************
 * @file RBEffectCompiler.cpp
 ***********************************************************************************************************/

/************************************************************************************************************
 * Includes
 ************************************************************************************************************/
#include "RBEffectCompiler.h"
#include "RBEffect.h"
#include "RBEffectFactory.h"
#include "RBEffectTechnique.h"
#include "RBEffectPass.h"
#include <FXParser.h>
#include <yaml-cpp/exceptions.h>
#include <yaml-cpp/node.h>
#include <yaml-cpp/parser.h>

#include <windows.h>

/************************************************************************************************************/

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBEffectCompiler::~RBEffectCompiler()
{
    for (RBEffectMap::iterator iter = m_mEffects.begin(); iter != m_mEffects.end(); ++iter)
    {
        delete iter->second;
    }
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBEffect* RBEffectCompiler::RetrieveEffect( const RBString& strEffectName )
{
    RBEffectMap::iterator iter =  m_mEffects.find(strEffectName);
    return iter != m_mEffects.end() ? iter->second : NULL;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBEffectCompiler::Compile( const RBString& strFilename )
{
    RBEffect* pEffect = new RBEffect;
    if (!CreateEffect(strFilename, *pEffect))
    {
        delete pEffect;
        return;
    }

    m_mEffects[pEffect->Name] = pEffect;

}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBEffectCompiler::Reset( bool bBeforeReset )
{
    for (RBEffectMap::iterator iter = m_mEffects.begin(); iter != m_mEffects.end(); ++iter)
    {
        if (bBeforeReset)
        {
            iter->second->DestroyTechniques();
        }
        else
        {
            CreateEffect(iter->second->Filename, *iter->second);
        }
    }
}

void operator >> (const YAML::Node& node, FXParser::PassDesc& pass_desc)
{
	node["name"] >> pass_desc.Name;

	static const char* const shaders_entry_points[] = {"vs_main", "ps_main", "gs_main", "cs_main", "hs_main"};

	for (uint32 ui=0; ui < ShaderType::ShaderCount; ++ui)
	{
		const YAML::Node* entry_point = node.FindValue(shaders_entry_points[ui]);
		if (entry_point != NULL)
		{
			RBString profile_entry;
			*entry_point >> profile_entry;
			size_t separator = profile_entry.find(' ');
			pass_desc.Shaders[ui].Profile = profile_entry.substr(0, separator);
			pass_desc.Shaders[ui].EntryPoint = profile_entry.substr(separator+1, RBString::npos);
		}
	}
}

void operator >> (const YAML::Node& node, FXParser::TechniqueDesc& technique_desc) 
{
	node["name"] >> technique_desc.Name;
	const YAML::Node& passes_node = node["passes"];

	for (size_t pass_idx=0; pass_idx<passes_node.size(); ++pass_idx)
	{
		FXParser::PassDesc pass_desc;
		passes_node[pass_idx] >> pass_desc;
		technique_desc.Passes.push_back(pass_desc);
	}
}


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
bool RBEffectCompiler::ParseEffect( const RBString& strFilename, FXParser::FXDesc* fxDesc, RBString& strToCompile )
{
    RBString shaderText;

	RBString shader_file = strFilename;
    RBStringTools::replace_extension(&shader_file, RBEffectFactory::ms_shader_ext);

	std::ifstream streamer(strFilename.c_str());
	YAML::Parser parser(streamer);
	YAML::Node doc;
    try
    {
        while(parser.GetNextDocument(doc)) 
        {
            const YAML::Node& techniques_nodes = doc["techniques"];
            for(unsigned i=0;i<techniques_nodes.size();i++) 
            {
                FXParser::TechniqueDesc tech_desc;
                techniques_nodes[i] >> tech_desc;
                fxDesc->Techniques.push_back(tech_desc);
            }
        }
    }    
    catch (const YAML::Exception& e)
    {
        OutputDebugStringA(e.msg.c_str());
    }

    if (!PreprocessShaderFile(&shaderText, shader_file))
    {
        return false;
    }

    strToCompile = shaderText;
    return true;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
bool RBEffectCompiler::CreateEffect( const RBString& strFilename, RBEffect& kEffect )
{
    FXParser::FXDesc fxDesc;
    RBString text;
    if (!ParseEffect(strFilename, &fxDesc, text))
    {
        return false;
    }

    kEffect.Name = RBStringTools::GetLeafName(strFilename);
    RBStringTools::replace_extension(&kEffect.Name, "", true);

    kEffect.Filename = strFilename;
    for (uint32 uiTechnique=0; uiTechnique < fxDesc.Techniques.size(); ++uiTechnique)
    {
        const FXParser::TechniqueDesc technique = fxDesc.Techniques[uiTechnique];
        RBEffectTechnique* pEffectTechnique = new RBEffectTechnique;
        kEffect.AddTechnique(technique.Name, pEffectTechnique);
        for (uint32 uiPass=0; uiPass<technique.Passes.size(); ++uiPass )
        {
            const FXParser::PassDesc& passDesc = technique.Passes[uiPass];
            RBEffectPass* pEffectPass = new RBEffectPass;
            pEffectTechnique->AddPass(pEffectPass);
            for (uint32 uiShader=0; uiShader < ShaderType::ShaderCount; ++uiShader)
            {
                pEffectPass->SetShader((EShaderType)uiShader, NULL);
                if (passDesc.Shaders[uiShader].EntryPoint.empty())
                {
                    continue;
                }
                RBShader* pShader = new RBShader;
                pShader->EntryPoint = passDesc.Shaders[uiShader].EntryPoint;
                pShader->Profile = passDesc.Shaders[uiShader].Profile;
                pEffectPass->SetShader((EShaderType)uiShader, pShader);
            }
        }
    }

    CompileNative(text, kEffect, fxDesc);

    return true;
}